Librerias

if(!require("jsonlite")){install.packages("jsonlite")}
Caricamento del pacchetto richiesto: jsonlite
Avvertimento: il pacchetto ‘jsonlite’ è stato creato con R versione 4.2.3
if(!require("httr")){install.packages("httr")}
Caricamento del pacchetto richiesto: httr
Avvertimento: il pacchetto ‘httr’ è stato creato con R versione 4.2.3
if(!require("data.table")){install.packages("data.table")}
Caricamento del pacchetto richiesto: data.table
data.table 1.14.2 using 2 threads (see ?getDTthreads).  Latest news: r-datatable.com

Caricamento pacchetto: ‘data.table’

I seguenti oggetti sono mascherati da ‘package:dplyr’:

    between, first, last
if(!require("tidyverse")){install.packages("tidyverse")}
Caricamento del pacchetto richiesto: tidyverse
Avvertimento: il pacchetto ‘tidyverse’ è stato creato con R versione 4.2.3Avvertimento: il pacchetto ‘tibble’ è stato creato con R versione 4.2.3Avvertimento: il pacchetto ‘tidyr’ è stato creato con R versione 4.2.3Avvertimento: il pacchetto ‘readr’ è stato creato con R versione 4.2.3Avvertimento: il pacchetto ‘purrr’ è stato creato con R versione 4.2.3Avvertimento: il pacchetto ‘stringr’ è stato creato con R versione 4.2.3Avvertimento: il pacchetto ‘forcats’ è stato creato con R versione 4.2.3Avvertimento: il pacchetto ‘lubridate’ è stato creato con R versione 4.2.3── Attaching core tidyverse packages ────────────────────────────────────────── tidyverse 2.0.0 ──
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ lubridate 1.9.2     ✔ tibble    3.2.1
✔ purrr     1.0.1     ✔ tidyr     1.3.0
✔ readr     2.1.4     ── Conflicts ──────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ data.table::between() masks dplyr::between()
✖ dplyr::filter()       masks stats::filter()
✖ data.table::first()   masks dplyr::first()
✖ purrr::flatten()      masks jsonlite::flatten()
✖ lubridate::hour()     masks data.table::hour()
✖ lubridate::isoweek()  masks data.table::isoweek()
✖ dplyr::lag()          masks stats::lag()
✖ data.table::last()    masks dplyr::last()
✖ lubridate::mday()     masks data.table::mday()
✖ lubridate::minute()   masks data.table::minute()
✖ lubridate::month()    masks data.table::month()
✖ lubridate::quarter()  masks data.table::quarter()
✖ lubridate::second()   masks data.table::second()
✖ purrr::transpose()    masks data.table::transpose()
✖ lubridate::wday()     masks data.table::wday()
✖ lubridate::week()     masks data.table::week()
✖ lubridate::yday()     masks data.table::yday()
✖ lubridate::year()     masks data.table::year()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
if(!require("scales")){install.packages("scales")}
Caricamento del pacchetto richiesto: scales

Caricamento pacchetto: ‘scales’

Il seguente oggetto è mascherato da ‘package:purrr’:

    discard

Il seguente oggetto è mascherato da ‘package:readr’:

    col_factor
if(!require("ggplot2")){install.packages("ggplot2")}
library(leaflet)
Avvertimento: il pacchetto ‘leaflet’ è stato creato con R versione 4.2.3Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
library(dplyr)
library(MASS)

Caricamento pacchetto: ‘MASS’

Il seguente oggetto è mascherato da ‘package:dplyr’:

    select
library(ggplot2)
library(RColorBrewer)
library(reticulate)
Avvertimento: il pacchetto ‘reticulate’ è stato creato con R versione 4.2.3
library(lime)

Caricamento pacchetto: ‘lime’

Il seguente oggetto è mascherato da ‘package:dplyr’:

    explain
library(e1071)  
Avvertimento: il pacchetto ‘e1071’ è stato creato con R versione 4.2.3
library(splines)
library(tidyverse)
library(data.table)
library(tseries)
Avvertimento: il pacchetto ‘tseries’ è stato creato con R versione 4.2.3Registered S3 method overwritten by 'quantmod':
  method            from
  as.zoo.data.frame zoo 

    ‘tseries’ version: 0.10-53

    ‘tseries’ is a package for time series analysis and computational finance.

    See ‘library(help="tseries")’ for details.
library(mgcv)
Caricamento del pacchetto richiesto: nlme

Caricamento pacchetto: ‘nlme’

Il seguente oggetto è mascherato da ‘package:dplyr’:

    collapse

This is mgcv 1.8-41. For overview type 'help("mgcv-package")'.
library(regclass)
Caricamento del pacchetto richiesto: bestglm
Caricamento del pacchetto richiesto: leaps
Caricamento del pacchetto richiesto: VGAM
Avvertimento: il pacchetto ‘VGAM’ è stato creato con R versione 4.2.3Caricamento del pacchetto richiesto: stats4

Caricamento pacchetto: ‘VGAM’

Il seguente oggetto è mascherato da ‘package:mgcv’:

    s

Caricamento del pacchetto richiesto: rpart
Caricamento del pacchetto richiesto: randomForest
randomForest 4.7-1.1
Type rfNews() to see new features/changes/bug fixes.

Caricamento pacchetto: ‘randomForest’

Il seguente oggetto è mascherato da ‘package:ggplot2’:

    margin

Il seguente oggetto è mascherato da ‘package:dplyr’:

    combine

Important regclass change from 1.3:
All functions that had a . in the name now have an _
all.correlations -> all_correlations, cor.demo -> cor_demo, etc.
library(glmnet)
Avvertimento: il pacchetto ‘glmnet’ è stato creato con R versione 4.2.3Caricamento del pacchetto richiesto: Matrix

Caricamento pacchetto: ‘Matrix’

I seguenti oggetti sono mascherati da ‘package:tidyr’:

    expand, pack, unpack

Loaded glmnet 4.1-6
library(coefplot)

Caricamento pacchetto: ‘coefplot’

Il seguente oggetto è mascherato da ‘package:e1071’:

    extractPath
library(ggplot2)

Per accedere alle API di idealista necessitiamo di ottenere le credenziali. Una volta ottenute le carichiamo in R. Inoltre, prepariamo anche altri parametri necessari al collegamento con il sito.

# parametri in input

consumer_key <- "xop6jrf8yhw02a514zwhxrhlplvgn4ip"
consumer_secret <- "upiSxn5JmYpy"

# nuove credenziali

consumer_key = "hpsdfxg76unhb6tgwe38qukjzacwyr3b"
consumer_secret = "Ch7SVGGp4Vp4"

#Use basic authentication
secret <- jsonlite::base64_enc(paste(consumer_key, consumer_secret, sep = ":"))
req <- httr::POST("https://api.idealista.com/oauth/token",
                  httr::add_headers(
                    #"Authorization" = paste("Basic", gsub("n", "", secret)),
                    "Authorization" = paste("Basic", secret, sep = " "),
                    "Content-Type" = "application/x-www-form-urlencoded;charset=utf-8"
                  ),
                  body = "grant_type=client_credentials"
)

token <- paste("Bearer", httr::content(req)$access_token)

Prepariamo i parametri in input che andranno a creare il link con la quale verrà eseguita la richiesta. Il nostro obiettivo è quello di scaricare tutti gli annunci delle case in vendita a Malaga. Come posizione è stata inserita la stazione Maria Zambrano ed stata impostata una distanza massima di 10 km. Come dimensione minima del locale è stato prefissato il valore di 30 per escludere eventuali annunci di garage.

#url user parameters
x = '36.71145256718129'
y = '-4.4288958904720355'
# x = '45.643170'
# y = '13.790524'
maxItems = '10000'
distance = '10000'
type = 'homes'
op = 'sale'
minprice = '30001'
maxprice = '200000000'
minsize = '30'
maxsize = '10000'


#url fixed parameters
site = 'https://api.idealista.com/3.5/es/search?'
# site = 'https://api.idealista.com/3.5/it/search?'
loc = 'center='
country = '&country=es'
# country = '&country=it'
maxitems = '&maxItems=50'
pages = '&numPage='
dist = '&distance='
property = '&propertyType='
operation = '&operation='
pricefrom = '&minPrice='
priceto = '&maxPrice='
misize = '&minSize='
masize = '&maxSize='
chalet = '&chalet=0'

Ora inoltreremo la richiesta di ricerca a idealista. Abbiamo un limite mensile di massimo 100 richieste (pagina = 100), e di massimo una richiesta al secondo (Sys.sleep(1)). Nel ciclo a ogni giro viene generata una richiesta che differisce unicamente sul numero della pagina dei risultati, andando così a prelevare tutti i dati di una singola ricerca.

Una volta scaricati i dati ed estratti dal JSON otteremo delle liste che a sua volta dovranno essere estratte e immagazzinate in un dataframe. Il problema emerge in quanto all’interno della lista sono presenti sotto liste e, oltretutto, non per ogni annuncio. Allora bisogna creare una matrice che per ogni richiesta abbia il numero di colonne pari alla grandezza massima di variabili differenti per un’osservazione e successivamente riempita. Infine i dati vengono uniti ai dati delle richieste precedenti, aggiungendo le eventuali nuove colonne al database già esistente.

pagina = 100

for(z in 1:pagina)
{
  print(z)
  
  # prepara l'url
  url <- paste(site, loc, x, ',', y, country, maxitems, pages, z, dist, distance,
               property, type, operation, op, pricefrom, minprice, priceto, maxprice,
               misize, minsize, masize, maxsize, sep = "")
  
  # invia la richiesta a idealista
  res <- httr::POST(url, httr::add_headers("Authorization" = token))
  
  # estrai il contenuto dal JSON 
  cont_raw <- httr::content(res) 
  
  
  # Va a cercare l'item con più colonne
  indexColMax = sapply(1:length(cont_raw[[1]]), function(x) cont_raw[[1]][[x]] %>% names() %>% length) %>% which.max
  colNames = cont_raw[[1]][[indexColMax]] %>% names()
  # Creo una matrice vuota dove imagazzinare i valori
  m = matrix(NA, nrow = length(cont_raw[[1]]), ncol = length(colNames))
  colnames(m) = colNames
  for(r in 1:length(cont_raw[[1]]))
  {
    for(c in 1:length(cont_raw[[1]][[r]]))
    {
      # nel caso l'elemento della lista sia una sotto lista o df vado a spacchettarlo aggiungendo colonne
      if(length(cont_raw[[1]][[r]][[c]])>1)
      {
        # non si può fare in un unico caso
        for(i in 1:length(cont_raw[[1]][[r]][[c]]))
        {
          # se la colonna della sottolista non è già stata aggiunta lo faccio
          if(is.null(names(cont_raw[[1]][[r]][[c]])))
          {
            cont_raw[[1]][[r]][[c]] = cont_raw[[1]][[r]][[c]][[1]] 
          }
          if(!names(cont_raw[[1]][[r]][[c]])[i] %in% colNames)
          {
            colNames = c(colNames, names(cont_raw[[1]][[r]][[c]])[i])
            m = cbind(m, rep(NA,length(cont_raw[[1]]))) # aggiunta della colonna
            colnames(m) = colNames
          }
        }
        # inserisco i dati della sottolista
        for(k in 1:length(cont_raw[[1]][[r]][[c]]))
          m[r,names(cont_raw[[1]][[r]][[c]])[k]] = cont_raw[[1]][[r]][[c]][[k]]
      }else{
        tryCatch(
          {
            m[r,names(cont_raw[[1]][[r]][c])] = ifelse(length(cont_raw[[1]][[r]][[c]][[1]])>1,
                                                       cont_raw[[1]][[r]][[c]][[1]][[1]],
                                                       cont_raw[[1]][[r]][[c]][[1]])
          },
          error = function(e) print(e, z, r, c))
      }
    }
  }
  data = m %>% data.frame() %>% tibble()
  
  # debug
  print(c(z,minprice,maxprice,data %>% dim))
  
  # merge database
  if(z == 1)
  {
    d = data
  }else
  {
    data[setdiff(names(d), names(data))] <- NA
    d[setdiff(names(d), names(data))] <- NA
    d = bind_rows(d, data)
  }
  
  Sys.sleep(1)
  
}

saveRDS(data, "dati idealista")

Pulizia dei dati

Viene eseguita una pulizia dei dati grossolana, modificando principalmente il tipo di variabile.

data.frame(1:dim(data)[2],data %>% names)
# pulizia dei dati
data$floor[data$floor == "bj"] = 0

indexNumeric = c(1,4,5,6,9,11,12,19,20,23,29,30,43)
data = data %>% mutate_at(indexNumeric, as.numeric)
Avvertimento: There were 2 warnings in `mutate()`.
The first warning was:
ℹ In argument: `floor = .Primitive("as.double")(floor)`.
Caused by warning:
! NAs introduced by coercion
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 1 remaining warning.
indexFactor = c(7,8,14,15,16,17,18,26,31,39)
data = data %>% mutate_at(indexFactor, as.factor)
indexLogic = c(10,21,25,28,33:38,41,42)
data = data %>% mutate_at(indexLogic, as.logical)

data

Analisi esplorativa dei dati

Istogramma del piano delle case nella quale si trovano.

ggplot(data %>% subset(!is.na(floor)), aes(x = floor)) +
  geom_histogram(bins = max(data$floor,na.rm = T)) +
  scale_x_continuous(breaks = pretty_breaks(max(data$floor,na.rm = T)))

Grafico a barre del prezzo medio delle case in rapporto al piano nella quale si trovano.

Boxplot del prezzo medio delle case in funzione del piano nella quale si trovano.

Distribuzione della dimensione delle case.

Prezzo delle case in funzione della loro dimensione. Gli assi non sono proporzionali ma sono logaritmici.

Prezzo delle case in funzione al numero di bagni che la casa possiede.

Mappa del prezzo delle case nelle diverse zone della città. La mappa è interattiva, cliccando sui singoli pallini comparirà una box con ulteriori dati sulla casa.

pal = with(data, colorFactor(brewer.pal(10,"RdYlGn"), -price))
dfPopup = data %>% 
  mutate(popup_info = paste("Prezzo della casa: ", price, " $", "</br>",
                            "Superficie: ", size, "</br>", 
                            "Piano: ", floor, "</br>", 
                            "Numero di bagni: ", bathrooms, "</br>",
                            "Numero di camere: ", rooms, "</br>",
                            "Zona: ", neighborhood, "</br>",
                            "Distanza: ", distance, "</br>"))
leaflet() %>% 
  addTiles() %>% 
  addCircleMarkers(data = dfPopup,
                   lat = ~ latitude,
                   lng = ~ longitude,
                   radius = ~ 2,
                   opacity = .7,
                   color = ~ pal(-price),
                   popup = ~ popup_info)

Correlogramma delle variabili più frequenti.

modLasso = glmnet(makeX(data[, !names(data) %in% c("price","parkingSpace")] %>% 
                          select_if(is.numeric) %>%
                          na.roughfix()),
                       data$price,
                       alpha = 1)
Error in glmnet(makeX(data[, !names(data) %in% c("price", "parkingSpace")] %>%  : 
  x has missing values; consider using makeX() to impute them
LS0tDQp0aXRsZTogIklkZWFsaXN0YV9BUEkiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCkF1dGhvcjogQW50b25pbyBNYXJ0aW4tQ29ib3MgfCBEYXRhIEFuYWx5dGljcyBNYW5hZ2VyDQotLS0NCg0KTGlicmVyaWFzDQoNCmBgYHtyfQ0KaWYoIXJlcXVpcmUoImpzb25saXRlIikpe2luc3RhbGwucGFja2FnZXMoImpzb25saXRlIil9DQppZighcmVxdWlyZSgiaHR0ciIpKXtpbnN0YWxsLnBhY2thZ2VzKCJodHRyIil9DQppZighcmVxdWlyZSgiZGF0YS50YWJsZSIpKXtpbnN0YWxsLnBhY2thZ2VzKCJkYXRhLnRhYmxlIil9DQppZighcmVxdWlyZSgidGlkeXZlcnNlIikpe2luc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpfQ0KaWYoIXJlcXVpcmUoInNjYWxlcyIpKXtpbnN0YWxsLnBhY2thZ2VzKCJzY2FsZXMiKX0NCmlmKCFyZXF1aXJlKCJnZ3Bsb3QyIikpe2luc3RhbGwucGFja2FnZXMoImdncGxvdDIiKX0NCmxpYnJhcnkobGVhZmxldCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkocmV0aWN1bGF0ZSkNCmxpYnJhcnkobGltZSkNCmxpYnJhcnkoZTEwNzEpICANCmxpYnJhcnkoc3BsaW5lcykNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeSh0c2VyaWVzKQ0KbGlicmFyeShtZ2N2KQ0KbGlicmFyeShyZWdjbGFzcykNCmxpYnJhcnkoZ2xtbmV0KQ0KbGlicmFyeShjb2VmcGxvdCkNCmxpYnJhcnkoZ2dwbG90MikNCmBgYA0KUGVyIGFjY2VkZXJlIGFsbGUgQVBJIGRpIGlkZWFsaXN0YSBuZWNlc3NpdGlhbW8gZGkgb3R0ZW5lcmUgbGUgY3JlZGVuemlhbGkuIA0KVW5hIHZvbHRhIG90dGVudXRlIGxlIGNhcmljaGlhbW8gaW4gUi4NCklub2x0cmUsIHByZXBhcmlhbW8gYW5jaGUgYWx0cmkgcGFyYW1ldHJpIG5lY2Vzc2FyaSBhbCBjb2xsZWdhbWVudG8gY29uIGlsIHNpdG8uDQoNCmBgYHtyfQ0KIyBwYXJhbWV0cmkgaW4gaW5wdXQNCg0KY29uc3VtZXJfa2V5IDwtICJ4b3A2anJmOHlodzAyYTUxNHp3aHhyaGxwbHZnbjRpcCINCmNvbnN1bWVyX3NlY3JldCA8LSAidXBpU3huNUptWXB5Ig0KDQojIG51b3ZlIGNyZWRlbnppYWxpDQoNCmNvbnN1bWVyX2tleSA9ICJocHNkZnhnNzZ1bmhiNnRnd2UzOHF1a2p6YWN3eXIzYiINCmNvbnN1bWVyX3NlY3JldCA9ICJDaDdTVkdHcDRWcDQiDQoNCiNVc2UgYmFzaWMgYXV0aGVudGljYXRpb24NCnNlY3JldCA8LSBqc29ubGl0ZTo6YmFzZTY0X2VuYyhwYXN0ZShjb25zdW1lcl9rZXksIGNvbnN1bWVyX3NlY3JldCwgc2VwID0gIjoiKSkNCnJlcSA8LSBodHRyOjpQT1NUKCJodHRwczovL2FwaS5pZGVhbGlzdGEuY29tL29hdXRoL3Rva2VuIiwNCiAgICAgICAgICAgICAgICAgIGh0dHI6OmFkZF9oZWFkZXJzKA0KICAgICAgICAgICAgICAgICAgICAjIkF1dGhvcml6YXRpb24iID0gcGFzdGUoIkJhc2ljIiwgZ3N1YigibiIsICIiLCBzZWNyZXQpKSwNCiAgICAgICAgICAgICAgICAgICAgIkF1dGhvcml6YXRpb24iID0gcGFzdGUoIkJhc2ljIiwgc2VjcmV0LCBzZXAgPSAiICIpLA0KICAgICAgICAgICAgICAgICAgICAiQ29udGVudC1UeXBlIiA9ICJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQ7Y2hhcnNldD11dGYtOCINCiAgICAgICAgICAgICAgICAgICksDQogICAgICAgICAgICAgICAgICBib2R5ID0gImdyYW50X3R5cGU9Y2xpZW50X2NyZWRlbnRpYWxzIg0KKQ0KDQp0b2tlbiA8LSBwYXN0ZSgiQmVhcmVyIiwgaHR0cjo6Y29udGVudChyZXEpJGFjY2Vzc190b2tlbikNCg0KYGBgDQoNClByZXBhcmlhbW8gaSBwYXJhbWV0cmkgaW4gaW5wdXQgY2hlIGFuZHJhbm5vIGEgY3JlYXJlIGlsIGxpbmsgY29uIGxhIHF1YWxlIHZlcnLDoCBlc2VndWl0YSBsYSByaWNoaWVzdGEuDQpJbCBub3N0cm8gb2JpZXR0aXZvIMOoIHF1ZWxsbyBkaSBzY2FyaWNhcmUgdHV0dGkgZ2xpIGFubnVuY2kgZGVsbGUgY2FzZSBpbiB2ZW5kaXRhIGEgTWFsYWdhLg0KQ29tZSBwb3NpemlvbmUgw6ggc3RhdGEgaW5zZXJpdGEgbGEgc3RhemlvbmUgTWFyaWEgWmFtYnJhbm8gZWQgIHN0YXRhIGltcG9zdGF0YSB1bmEgZGlzdGFuemEgbWFzc2ltYSBkaSAxMCBrbS4NCkNvbWUgZGltZW5zaW9uZSBtaW5pbWEgZGVsIGxvY2FsZSAgw6ggc3RhdG8gcHJlZmlzc2F0byBpbCB2YWxvcmUgZGkgMzAgcGVyIGVzY2x1ZGVyZSBldmVudHVhbGkgYW5udW5jaSBkaSBnYXJhZ2UuDQoNCmBgYHtyfQ0KI3VybCB1c2VyIHBhcmFtZXRlcnMNCnggPSAnMzYuNzExNDUyNTY3MTgxMjknDQp5ID0gJy00LjQyODg5NTg5MDQ3MjAzNTUnDQojIHggPSAnNDUuNjQzMTcwJw0KIyB5ID0gJzEzLjc5MDUyNCcNCm1heEl0ZW1zID0gJzEwMDAwJw0KZGlzdGFuY2UgPSAnMTAwMDAnDQp0eXBlID0gJ2hvbWVzJw0Kb3AgPSAnc2FsZScNCm1pbnByaWNlID0gJzMwMDAxJw0KbWF4cHJpY2UgPSAnMjAwMDAwMDAwJw0KbWluc2l6ZSA9ICczMCcNCm1heHNpemUgPSAnMTAwMDAnDQoNCg0KI3VybCBmaXhlZCBwYXJhbWV0ZXJzDQpzaXRlID0gJ2h0dHBzOi8vYXBpLmlkZWFsaXN0YS5jb20vMy41L2VzL3NlYXJjaD8nDQojIHNpdGUgPSAnaHR0cHM6Ly9hcGkuaWRlYWxpc3RhLmNvbS8zLjUvaXQvc2VhcmNoPycNCmxvYyA9ICdjZW50ZXI9Jw0KY291bnRyeSA9ICcmY291bnRyeT1lcycNCiMgY291bnRyeSA9ICcmY291bnRyeT1pdCcNCm1heGl0ZW1zID0gJyZtYXhJdGVtcz01MCcNCnBhZ2VzID0gJyZudW1QYWdlPScNCmRpc3QgPSAnJmRpc3RhbmNlPScNCnByb3BlcnR5ID0gJyZwcm9wZXJ0eVR5cGU9Jw0Kb3BlcmF0aW9uID0gJyZvcGVyYXRpb249Jw0KcHJpY2Vmcm9tID0gJyZtaW5QcmljZT0nDQpwcmljZXRvID0gJyZtYXhQcmljZT0nDQptaXNpemUgPSAnJm1pblNpemU9Jw0KbWFzaXplID0gJyZtYXhTaXplPScNCmNoYWxldCA9ICcmY2hhbGV0PTAnDQoNCmBgYA0KDQpPcmEgIGlub2x0cmVyZW1vIGxhIHJpY2hpZXN0YSBkaSByaWNlcmNhIGEgaWRlYWxpc3RhLiANCkFiYmlhbW8gdW4gbGltaXRlIG1lbnNpbGUgZGkgbWFzc2ltbyAxMDAgcmljaGllc3RlIChgcGFnaW5hID0gMTAwYCksIGUgZGkgbWFzc2ltbyB1bmEgcmljaGllc3RhIGFsIHNlY29uZG8gKGBTeXMuc2xlZXAoMSlgKS4NCk5lbCBjaWNsbyBhIG9nbmkgZ2lybyB2aWVuZSBnZW5lcmF0YSB1bmEgcmljaGllc3RhIGNoZSBkaWZmZXJpc2NlIHVuaWNhbWVudGUgc3VsIG51bWVybyBkZWxsYSBwYWdpbmEgZGVpIHJpc3VsdGF0aSwgYW5kYW5kbyBjb3PDrCBhIHByZWxldmFyZSB0dXR0aSBpIGRhdGkgZGkgdW5hIHNpbmdvbGEgIHJpY2VyY2EuDQoNClVuYSB2b2x0YSBzY2FyaWNhdGkgaSBkYXRpIGVkIGVzdHJhdHRpIGRhbCBKU09OIG90dGVyZW1vIGRlbGxlIGxpc3RlIGNoZSBhIHN1YSB2b2x0YSAgZG92cmFubm8gZXNzZXJlIGVzdHJhdHRlIGUgaW1tYWdhenppbmF0ZSBpbiB1biBkYXRhZnJhbWUuDQpJbCBwcm9ibGVtYSBlbWVyZ2UgaW4gcXVhbnRvIGFsbCdpbnRlcm5vIGRlbGxhIGxpc3RhIHNvbm8gcHJlc2VudGkgc290dG8gbGlzdGUgZSwgb2x0cmV0dXR0bywgbm9uIHBlciBvZ25pIGFubnVuY2lvLiANCkFsbG9yYSBiaXNvZ25hIGNyZWFyZSB1bmEgbWF0cmljZSBjaGUgcGVyIG9nbmkgcmljaGllc3RhIGFiYmlhIGlsIG51bWVybyBkaSBjb2xvbm5lIHBhcmkgYWxsYSBncmFuZGV6emEgbWFzc2ltYSBkaSB2YXJpYWJpbGkgZGlmZmVyZW50aSBwZXIgdW4nb3NzZXJ2YXppb25lIGUgc3VjY2Vzc2l2YW1lbnRlIHJpZW1waXRhLg0KSW5maW5lIGkgZGF0aSB2ZW5nb25vIHVuaXRpIGFpIGRhdGkgZGVsbGUgcmljaGllc3RlIHByZWNlZGVudGksIGFnZ2l1bmdlbmRvIGxlIGV2ZW50dWFsaSBudW92ZSBjb2xvbm5lIGFsIGRhdGFiYXNlIGdpw6AgZXNpc3RlbnRlLg0KDQpgYGB7cn0NCnBhZ2luYSA9IDEwMA0KDQpmb3IoeiBpbiAxOnBhZ2luYSkNCnsNCiAgcHJpbnQoeikNCiAgDQogICMgcHJlcGFyYSBsJ3VybA0KICB1cmwgPC0gcGFzdGUoc2l0ZSwgbG9jLCB4LCAnLCcsIHksIGNvdW50cnksIG1heGl0ZW1zLCBwYWdlcywgeiwgZGlzdCwgZGlzdGFuY2UsDQogICAgICAgICAgICAgICBwcm9wZXJ0eSwgdHlwZSwgb3BlcmF0aW9uLCBvcCwgcHJpY2Vmcm9tLCBtaW5wcmljZSwgcHJpY2V0bywgbWF4cHJpY2UsDQogICAgICAgICAgICAgICBtaXNpemUsIG1pbnNpemUsIG1hc2l6ZSwgbWF4c2l6ZSwgc2VwID0gIiIpDQogIA0KICAjIGludmlhIGxhIHJpY2hpZXN0YSBhIGlkZWFsaXN0YQ0KICByZXMgPC0gaHR0cjo6UE9TVCh1cmwsIGh0dHI6OmFkZF9oZWFkZXJzKCJBdXRob3JpemF0aW9uIiA9IHRva2VuKSkNCiAgDQogICMgZXN0cmFpIGlsIGNvbnRlbnV0byBkYWwgSlNPTiANCiAgY29udF9yYXcgPC0gaHR0cjo6Y29udGVudChyZXMpIA0KICANCiAgDQogICMgVmEgYSBjZXJjYXJlIGwnaXRlbSBjb24gcGnDuSBjb2xvbm5lDQogIGluZGV4Q29sTWF4ID0gc2FwcGx5KDE6bGVuZ3RoKGNvbnRfcmF3W1sxXV0pLCBmdW5jdGlvbih4KSBjb250X3Jhd1tbMV1dW1t4XV0gJT4lIG5hbWVzKCkgJT4lIGxlbmd0aCkgJT4lIHdoaWNoLm1heA0KICBjb2xOYW1lcyA9IGNvbnRfcmF3W1sxXV1bW2luZGV4Q29sTWF4XV0gJT4lIG5hbWVzKCkNCiAgIyBDcmVvIHVuYSBtYXRyaWNlIHZ1b3RhIGRvdmUgaW1hZ2F6emluYXJlIGkgdmFsb3JpDQogIG0gPSBtYXRyaXgoTkEsIG5yb3cgPSBsZW5ndGgoY29udF9yYXdbWzFdXSksIG5jb2wgPSBsZW5ndGgoY29sTmFtZXMpKQ0KICBjb2xuYW1lcyhtKSA9IGNvbE5hbWVzDQogIGZvcihyIGluIDE6bGVuZ3RoKGNvbnRfcmF3W1sxXV0pKQ0KICB7DQogICAgZm9yKGMgaW4gMTpsZW5ndGgoY29udF9yYXdbWzFdXVtbcl1dKSkNCiAgICB7DQogICAgICAjIG5lbCBjYXNvIGwnZWxlbWVudG8gZGVsbGEgbGlzdGEgc2lhIHVuYSBzb3R0byBsaXN0YSBvIGRmIHZhZG8gYSBzcGFjY2hldHRhcmxvIGFnZ2l1bmdlbmRvIGNvbG9ubmUNCiAgICAgIGlmKGxlbmd0aChjb250X3Jhd1tbMV1dW1tyXV1bW2NdXSk+MSkNCiAgICAgIHsNCiAgICAgICAgIyBub24gc2kgcHXDsiBmYXJlIGluIHVuIHVuaWNvIGNhc28NCiAgICAgICAgZm9yKGkgaW4gMTpsZW5ndGgoY29udF9yYXdbWzFdXVtbcl1dW1tjXV0pKQ0KICAgICAgICB7DQogICAgICAgICAgIyBzZSBsYSBjb2xvbm5hIGRlbGxhIHNvdHRvbGlzdGEgbm9uIMOoIGdpw6Agc3RhdGEgYWdnaXVudGEgbG8gZmFjY2lvDQogICAgICAgICAgaWYoaXMubnVsbChuYW1lcyhjb250X3Jhd1tbMV1dW1tyXV1bW2NdXSkpKQ0KICAgICAgICAgIHsNCiAgICAgICAgICAgIGNvbnRfcmF3W1sxXV1bW3JdXVtbY11dID0gY29udF9yYXdbWzFdXVtbcl1dW1tjXV1bWzFdXSANCiAgICAgICAgICB9DQogICAgICAgICAgaWYoIW5hbWVzKGNvbnRfcmF3W1sxXV1bW3JdXVtbY11dKVtpXSAlaW4lIGNvbE5hbWVzKQ0KICAgICAgICAgIHsNCiAgICAgICAgICAgIGNvbE5hbWVzID0gYyhjb2xOYW1lcywgbmFtZXMoY29udF9yYXdbWzFdXVtbcl1dW1tjXV0pW2ldKQ0KICAgICAgICAgICAgbSA9IGNiaW5kKG0sIHJlcChOQSxsZW5ndGgoY29udF9yYXdbWzFdXSkpKSAjIGFnZ2l1bnRhIGRlbGxhIGNvbG9ubmENCiAgICAgICAgICAgIGNvbG5hbWVzKG0pID0gY29sTmFtZXMNCiAgICAgICAgICB9DQogICAgICAgIH0NCiAgICAgICAgIyBpbnNlcmlzY28gaSBkYXRpIGRlbGxhIHNvdHRvbGlzdGENCiAgICAgICAgZm9yKGsgaW4gMTpsZW5ndGgoY29udF9yYXdbWzFdXVtbcl1dW1tjXV0pKQ0KICAgICAgICAgIG1bcixuYW1lcyhjb250X3Jhd1tbMV1dW1tyXV1bW2NdXSlba11dID0gY29udF9yYXdbWzFdXVtbcl1dW1tjXV1bW2tdXQ0KICAgICAgfWVsc2V7DQogICAgICAgIHRyeUNhdGNoKA0KICAgICAgICAgIHsNCiAgICAgICAgICAgIG1bcixuYW1lcyhjb250X3Jhd1tbMV1dW1tyXV1bY10pXSA9IGlmZWxzZShsZW5ndGgoY29udF9yYXdbWzFdXVtbcl1dW1tjXV1bWzFdXSk+MSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250X3Jhd1tbMV1dW1tyXV1bW2NdXVtbMV1dW1sxXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udF9yYXdbWzFdXVtbcl1dW1tjXV1bWzFdXSkNCiAgICAgICAgICB9LA0KICAgICAgICAgIGVycm9yID0gZnVuY3Rpb24oZSkgcHJpbnQoZSwgeiwgciwgYykpDQogICAgICB9DQogICAgfQ0KICB9DQogIGRhdGEgPSBtICU+JSBkYXRhLmZyYW1lKCkgJT4lIHRpYmJsZSgpDQogIA0KICAjIGRlYnVnDQogIHByaW50KGMoeixtaW5wcmljZSxtYXhwcmljZSxkYXRhICU+JSBkaW0pKQ0KICANCiAgIyBtZXJnZSBkYXRhYmFzZQ0KICBpZih6ID09IDEpDQogIHsNCiAgICBkID0gZGF0YQ0KICB9ZWxzZQ0KICB7DQogICAgZGF0YVtzZXRkaWZmKG5hbWVzKGQpLCBuYW1lcyhkYXRhKSldIDwtIE5BDQogICAgZFtzZXRkaWZmKG5hbWVzKGQpLCBuYW1lcyhkYXRhKSldIDwtIE5BDQogICAgZCA9IGJpbmRfcm93cyhkLCBkYXRhKQ0KICB9DQogIA0KICBTeXMuc2xlZXAoMSkNCiAgDQp9DQoNCnNhdmVSRFMoZGF0YSwgImRhdGkgaWRlYWxpc3RhIikNCmBgYA0KDQojIyBQdWxpemlhIGRlaSBkYXRpDQoNClZpZW5lIGVzZWd1aXRhIHVuYSBwdWxpemlhIGRlaSBkYXRpIGdyb3Nzb2xhbmEsIG1vZGlmaWNhbmRvIHByaW5jaXBhbG1lbnRlIGlsIHRpcG8gZGkgdmFyaWFiaWxlLg0KDQpgYGB7cn0NCmRhdGEuZnJhbWUoMTpkaW0oZGF0YSlbMl0sZGF0YSAlPiUgbmFtZXMpDQojIHB1bGl6aWEgZGVpIGRhdGkNCmRhdGEkZmxvb3JbZGF0YSRmbG9vciA9PSAiYmoiXSA9IDANCg0KaW5kZXhOdW1lcmljID0gYygxLDQsNSw2LDksMTEsMTIsMTksMjAsMjMsMjksMzAsNDMpDQpkYXRhID0gZGF0YSAlPiUgbXV0YXRlX2F0KGluZGV4TnVtZXJpYywgYXMubnVtZXJpYykNCmluZGV4RmFjdG9yID0gYyg3LDgsMTQsMTUsMTYsMTcsMTgsMjYsMzEsMzkpDQpkYXRhID0gZGF0YSAlPiUgbXV0YXRlX2F0KGluZGV4RmFjdG9yLCBhcy5mYWN0b3IpDQppbmRleExvZ2ljID0gYygxMCwyMSwyNSwyOCwzMzozOCw0MSw0MikNCmRhdGEgPSBkYXRhICU+JSBtdXRhdGVfYXQoaW5kZXhMb2dpYywgYXMubG9naWNhbCkNCg0KZGF0YQ0KYGBgDQoNCiMgQW5hbGlzaSBlc3Bsb3JhdGl2YSAgZGVpIGRhdGkNCg0KSXN0b2dyYW1tYSBkZWwgcGlhbm8gZGVsbGUgY2FzZSBuZWxsYSBxdWFsZSBzaSB0cm92YW5vLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhICU+JSBzdWJzZXQoIWlzLm5hKGZsb29yKSksIGFlcyh4ID0gZmxvb3IpKSArDQogIGdlb21faGlzdG9ncmFtKGJpbnMgPSBtYXgoZGF0YSRmbG9vcixuYS5ybSA9IFQpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBwcmV0dHlfYnJlYWtzKG1heChkYXRhJGZsb29yLG5hLnJtID0gVCkpKQ0KYGBgDQpHcmFmaWNvIGEgYmFycmUgZGVsIHByZXp6byBtZWRpbyBkZWxsZSBjYXNlIGluIHJhcHBvcnRvIGFsIHBpYW5vIG5lbGxhIHF1YWxlIHNpIHRyb3Zhbm8uDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgJT4lIA0KICAgICAgICAgc3Vic2V0KCFpcy5uYShmbG9vcikpICU+JSANCiAgICAgICAgIGdyb3VwX2J5KGZsb29yKSAlPiUgDQogICAgICAgICBzdW1tYXJpc2UocHJpY2UgPSBtZWFuKHByaWNlKSksDQogICAgICAgYWVzKHggPSBmbG9vciwgeSA9IHByaWNlKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHByZXR0eV9icmVha3MobWF4KGRhdGEkZmxvb3IsbmEucm0gPSBUKSksLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gIuKCrCIpKQ0KYGBgDQoNCkJveHBsb3QgZGVsIHByZXp6byBtZWRpbyBkZWxsZSBjYXNlIGluIGZ1bnppb25lIGRlbCBwaWFubyBuZWxsYSBxdWFsZSBzaSB0cm92YW5vLiANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSAlPiUgbXV0YXRlKGZsb29yID0gYXMuZmFjdG9yKGZsb29yKSkgJT4lICBzdWJzZXQoIWlzLm5hKGZsb29yKSksDQogICAgICAgYWVzKHggPSBmbG9vciwgeSA9IHByaWNlKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIHNjYWxlX3lfbG9nMTAobGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gIuKCrCIpKQ0KYGBgDQoNCkRpc3RyaWJ1emlvbmUgZGVsbGEgZGltZW5zaW9uZSBkZWxsZSBjYXNlLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhICU+JSBzdWJzZXQoIWlzLm5hKHNpemUpKSwgYWVzKHggPSBzaXplKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwKSArDQogIHNjYWxlX3hfY29udGludW91cyh0cmFucyA9IGxvZzEwX3RyYW5zKCkpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSBmbG9vciwgeSA9IHByaWNlKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAiZ2FtIiwNCiAgICAgICAgICAgICAgZm9ybXVsYSA9IHkgfiBicyh4KSkgKyANCiAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OnVuaXRfZm9ybWF0KHVuaXQgPSAi4oKsIikpICsgDQogIHNjYWxlX3hfY29udGludW91cyhuLmJyZWFrcyA9IG1heChkYXRhJGZsb29yLG5hLnJtID0gVCkpDQpgYGANCg0KUHJlenpvIGRlbGxlIGNhc2UgaW4gZnVuemlvbmUgZGVsbGEgbG9ybyBkaW1lbnNpb25lLiANCkdsaSBhc3NpIG5vbiBzb25vIHByb3Bvcnppb25hbGkgbWEgc29ubyBsb2dhcml0bWljaS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSBzaXplLCB5ID0gcHJpY2UpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKyANCiAgc2NhbGVfeF9sb2cxMCgpICsNCiAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OnVuaXRfZm9ybWF0KHVuaXQgPSAi4oKsIikpDQpgYGANCg0KUHJlenpvIGRlbGxlIGNhc2UgaW4gZnVuemlvbmUgYWwgbnVtZXJvIGRpIGJhZ25pIGNoZSBsYSBjYXNhIHBvc3NpZWRlLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhICU+JSBtdXRhdGUoYmF0aHJvb21zID0gYXMuZmFjdG9yKGJhdGhyb29tcykpLA0KICAgICAgIGFlcyh4ID0gYmF0aHJvb21zLCB5ID0gcHJpY2UpKSArDQogIGdlb21fYm94cGxvdCgpICsgDQogIHNjYWxlX3hfZGlzY3JldGUoYnJlYWtzID0gcHJldHR5X2JyZWFrcyhtYXgoZGF0YSRiYXRocm9vbXMpKSkgKw0KICBzY2FsZV95X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6dW5pdF9mb3JtYXQodW5pdCA9ICLigqwiKSkNCmBgYA0KTWFwcGEgZGVsIHByZXp6byBkZWxsZSBjYXNlIG5lbGxlIGRpdmVyc2Ugem9uZSBkZWxsYSBjaXR0w6AuIA0KTGEgbWFwcGEgw6ggaW50ZXJhdHRpdmEsIGNsaWNjYW5kbyBzdWkgc2luZ29saSBwYWxsaW5pIGNvbXBhcmlyw6AgdW5hIGJveCBjb24gdWx0ZXJpb3JpIGRhdGkgc3VsbGEgY2FzYS4NCg0KYGBge3J9DQpwYWwgPSB3aXRoKGRhdGEsIGNvbG9yRmFjdG9yKGJyZXdlci5wYWwoMTAsIlJkWWxHbiIpLCAtcHJpY2UpKQ0KZGZQb3B1cCA9IGRhdGEgJT4lIA0KICBtdXRhdGUocG9wdXBfaW5mbyA9IHBhc3RlKCJQcmV6em8gZGVsbGEgY2FzYTogIiwgcHJpY2UsICIgJCIsICI8L2JyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlN1cGVyZmljaWU6ICIsIHNpemUsICI8L2JyPiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQaWFubzogIiwgZmxvb3IsICI8L2JyPiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJOdW1lcm8gZGkgYmFnbmk6ICIsIGJhdGhyb29tcywgIjwvYnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTnVtZXJvIGRpIGNhbWVyZTogIiwgcm9vbXMsICI8L2JyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlpvbmE6ICIsIG5laWdoYm9yaG9vZCwgIjwvYnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRGlzdGFuemE6ICIsIGRpc3RhbmNlLCAiPC9icj4iKSkNCmxlYWZsZXQoKSAlPiUgDQogIGFkZFRpbGVzKCkgJT4lIA0KICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBkZlBvcHVwLA0KICAgICAgICAgICAgICAgICAgIGxhdCA9IH4gbGF0aXR1ZGUsDQogICAgICAgICAgICAgICAgICAgbG5nID0gfiBsb25naXR1ZGUsDQogICAgICAgICAgICAgICAgICAgcmFkaXVzID0gfiAyLA0KICAgICAgICAgICAgICAgICAgIG9wYWNpdHkgPSAuNywNCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IH4gcGFsKC1wcmljZSksDQogICAgICAgICAgICAgICAgICAgcG9wdXAgPSB+IHBvcHVwX2luZm8pDQpgYGANCg0KDQpDb3JyZWxvZ3JhbW1hIGRlbGxlIHZhcmlhYmlsaSBwacO5IGZyZXF1ZW50aS4NCg0KYGBge3J9DQpkYXRhICU+JSANCiAgc2VsZWN0X2lmKGlzLm51bWVyaWMpICU+JQ0KICBkcGx5cjo6c2VsZWN0KCFjKHBhcmtpbmdTcGFjZSxkaXN0YW5jZSxwcm9wZXJ0eUNvZGUscGFya2luZ1NwYWNlLHN1YnRpdGxlKSkgJT4lIA0KICBuYS5yb3VnaGZpeCgpICU+JSAgDQogIGNvciAlPiUgDQogIGNvcnJwbG90Ojpjb3JycGxvdChtZXRob2QgPSAibnVtYmVyIiwNCiAgICAgICAgICAgICAgICAgICAgIGhjbHVzdC5tZXRob2QgPSAid2FyZC5EMiIsDQogICAgICAgICAgICAgICAgICAgICBkaWFnID0gRiwNCiAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAidXBwZXIiLA0KICAgICAgICAgICAgICAgICAgICAgb3JkZXIgPSAiaGNsdXN0IiwNCiAgICAgICAgICAgICAgICAgICAgIG51bWJlci5jZXggPSAuNikNCmBgYA0KDQoNCmBgYHtyfQ0KbW9kTGFzc28gPSBnbG1uZXQobWFrZVgoZGF0YVssICFuYW1lcyhkYXRhKSAlaW4lIGMoInByaWNlIiwicGFya2luZ1NwYWNlIildICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0X2lmKGlzLm51bWVyaWMpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5yb3VnaGZpeCgpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSRwcmljZSwNCiAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAxKQ0KY29lZnBsb3QobW9kTGFzc28sDQogICAgICAgICBpbnRlcmNlcHQgPSBGLA0KICAgICAgICAgaW50ZXJhY3RpdmUgPSBULGNleCA9IDAuNSwNCiAgICAgICAgIGNvZWZmaWNpZW50cyA9IG5hbWVzKGRhdGFbLCAhbmFtZXMoZGF0YSkgJWluJSBjKCJwcmljZSIsInBhcmtpbmdTcGFjZSIpXSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdF9pZihpcy5udW1lcmljKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbmEucm91Z2hmaXgoKSkpDQpgYGANCg0K